#include "defines.h"
#include "serial.h"
#include "lib.h"
#include "xmodem.h"
#include "elf.h"
#include "interrupt.h"
#include "dram.h"
#include "delay.h"

#define BUFSIZE 16

static int getline(unsigned char *buf)
{
    int i = 0, c;

    while (1) {
        c = serial_recv_byte(SERIAL_DEFAULT_DEVICE);

        if (c == -1) return -1;

        if (c == '\r') {
            putc('\n');
            buf[i++] = '\0';
            break;
        }

        if (c == '\b') {
            if (i > 0) {
                puts("\b \b");
                i--;
            }
        } else if (i < BUFSIZE - 1) {
            putc((unsigned char)c);
            buf[i++] = c;
        }
    }

    return i-1;
}

#define P1DDR   ((volatile uint8 *)0xfee000)
#define P2DDR   ((volatile uint8 *)0xfee001)
#define P6DDR   ((volatile uint8 *)0xfee005)
#define P8DDR   ((volatile uint8 *)0xfee007)

static void port_init()
{
    *P1DDR = 0xff;
    *P2DDR = 0x07;
    *P6DDR = 0xb0;
    *P8DDR = 0xec;
}

static int init(void)
{
    extern int erodata, data_start, edata, bss_start, ebss;

    memcpy(&data_start, &erodata, (long)&edata - (long)&data_start);
    memset(&bss_start, 0, (long)&ebss - (long)&bss_start);

    softvec_init();

    serial_init(SERIAL_DEFAULT_DEVICE);

    port_init();

    dram_init();

    /* nic_init() ? */

    return 0;
}

static int dump(char * buf, long size)
{
    long i;

    if (size < 0) {
        puts("no data.\n");
        return -1;
    }

    for (i = 0; i < size; i++) {
        putxval(buf[i], 2);
        if ((i & 0xf) == 0xf) {
            puts("\n");
        } else {
            if ((i & 0xf) == 7)
                puts(" ");
            puts(" ");
        }
    }
    puts("\n");

    return 0;
}

static char *COMMANDS[] = {"boot", "dump", "help", "load", "loadrun", "run", NULL};

int main(void)
{
    static char buf[BUFSIZE];
    static long size = -1;
    static unsigned char *loadbuf = NULL;
    extern int buffer_start, bootbuf_start;
    char *entry_point;
    void (*f)(void);

    INTR_DISABLE;

    init();

    puts("kzload (KOZOS boot loader) started.\n");

    while (1) {
        int i;

        puts("kzload> ");

        i = getline(buf);

        if (i == -1) {
            puts("serial error !!\n");
            serial_error_clear(SERIAL_DEFAULT_DEVICE);
            continue;
        }

        if (!strncmp(buf, "loadrun", 7)) {
            if (xmodem_set_nak_interval(atoi(buf + 7)) < 0) {
                puts("Invalid interval.\n");
                continue;
            }                

            loadbuf = (char *)(&buffer_start);
            size = xmodem_recv(loadbuf);
            delay_s(1);
            if (size < 0) {
                puts("XMODEM receive failed !!\n");
            } else {
                puts("XMODEM receive succeeded.\n");
            }

            entry_point = elf_load(loadbuf);
            if(!entry_point) {
                puts("run error !!\n");
            } else {
                puts("starting from enrty point: ");
                putxval((unsigned long)entry_point, 0);
                puts("\n");
                f = (void (*)(void))entry_point;
                f();
            }

        } else if (!strncmp(buf, "load", 4)) {
            if (xmodem_set_nak_interval(atoi(buf + 4)) < 0) {
                puts("Invalid interval.\n");
                continue;
            }                

            loadbuf = (char *)(&buffer_start);
            size = xmodem_recv(loadbuf);

            delay_s(1); /* もしかするとputsがXMODEMに行ってしまうから? */

            if (size < 0) {
                puts("XMODEM receive failed !!\n");
            } else {
                puts("XMODEM receive succeeded.\n");
            }

        } else if (!strcmp(buf, "dump")) {
            puts("size: "); putxval(size, 0); puts("\n");
            dump(loadbuf, size);

        } else if (!strcmp(buf, "run")) {
            entry_point = elf_load(loadbuf);
            if(!entry_point) {
                puts("run error !!\n");
            } else {
                puts("starting from enrty point: ");
                putxval((unsigned long)entry_point, 0);
                puts("\n");
                f = (void (*)(void))entry_point;
                f();
            }

        } else if (!strncmp(buf, "boot", 4)) { /* 転送しながら直接ロード */
            if (xmodem_set_nak_interval(atoi(buf + 4)) < 0) {
                puts("Invalid interval.\n");
                continue;
            }                

            loadbuf = (char *)(&bootbuf_start);
            entry_point = elf_boot(loadbuf, loadbuf + XMODEM_BLOCK_SIZE);

            delay_s(1);

            if(!entry_point) {
                puts("boot failed !!\n");
            } else {
                puts("starting from enrty point: ");
                putxval((unsigned long)entry_point, 0);
                puts("\n");
                f = (void (*)(void))entry_point;
                f();
            }

        } else if (!strcmp(buf, "help")) {
            char **p;
            for(p = COMMANDS; *p; p++) {
                puts(*p);
                puts("\n");
            }

        } else {
            puts("unknown command.\n");
        }
    }

    return 0;
}
